C.S.Burner 🪙 ━►🔥GIT Node
Node / reticulum_mirror / MeshChatX / files / meshchatx / src / frontend / components / ping / PingPage.vue
Displaying Raw • Download
meshchatx/src/frontend/components/ping/PingPage.vue 65ca9ddc823fcc2c8f8653c553b944b85db0875f (65ca9ddc) Text, 14.33 KB
T8b949e<!-- SPDX-License-Identifier: 0BSD AND MIT -->
Tff7b72<templateTff7b72>
T8b949e<!-- eslint-disable vue/no-v-html -->
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-col flex-1 overflow-hidden min-w-0 bg-slate-50 dark:bg-zinc-950Ta5d6ff"Tff7b72>
Tff7b72<ToolsPageHeader
Te6edf3icon=Ta5d6ff"Ta5d6ffradarTa5d6ff"
Te6edf3:title=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffping.titleTa5d6ff'Ta5d6ff)Ta5d6ff"
Te6edf3:description=Ta5d6ff"Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6fftools.ping.descriptionTa5d6ff'Ta5d6ff)Ta5d6ff"
Te6edf3accent=Ta5d6ff"Ta5d6ffblueTa5d6ff"
Tff7b72/>
Tff7b72<div
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 overflow-y-auto w-full px-4 md:px-5 lg:px-8 py-6 pb-[max(1.5rem,env(safe-area-inset-bottom))]Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffspace-y-4 w-full max-w-4xl mx-autoTa5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffglass-card space-y-5Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-sm text-gray-600 dark:text-gray-300Ta5d6ff"Tff7b72>
T8b949e<!-- eslint-disable-next-line vue/no-v-html -->
Tff7b72<span
Te6edf3v-html=Ta5d6ff"
Ta5d6ff Te6edf3$tTa5d6ff(Ta5d6ff'Ta5d6ffping.descriptionTa5d6ff'Ta5d6ff, {
code: Ta5d6ff'Ta5d6ff<code class=\Ta5d6ff'Ta5d6fffont-mono text-xs\Ta5d6ff'Ta5d6ff>lxmf.delivery</code>Ta5d6ff'Ta5d6ff,
})
Ta5d6ff"
Tff7b72>Tff7b72</span>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffgrid md:grid-cols-2 gap-4Ta5d6ff"Tff7b72>
Tff7b72<divTff7b72>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6ffglass-labelTa5d6ff"Tff7b72>{{ Te6edf3$t("ping.destination_hash") }}Tff7b72</label>
Tff7b72<input
Te6edf3v-model=Ta5d6ff"Ta5d6ffdestinationHashTa5d6ff"
Te6edf3type=Ta5d6ff"Ta5d6fftextTa5d6ff"
Te6edf3placeholder=Ta5d6ff"Ta5d6ffe.g. 7b746057a7294469799cd8d7d429676aTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffinput-field font-monoTa5d6ff"
Tff7b72/>
Tff7b72</div>
Tff7b72<divTff7b72>
Tff7b72<label Te6edf3class=Ta5d6ff"Ta5d6ffglass-labelTa5d6ff"Tff7b72>{{ Te6edf3$t("ping.timeout_seconds") }}Tff7b72</label>
Tff7b72<input Te6edf3v-model=Ta5d6ff"Ta5d6fftimeoutTa5d6ff" Te6edf3type=Ta5d6ff"Ta5d6ffnumberTa5d6ff" Te6edf3min=Ta5d6ff"Ta5d6ff1Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffinput-fieldTa5d6ff" Tff7b72/>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-2Ta5d6ff"Tff7b72>
Tff7b72<button Te6edf3v-if=Ta5d6ff"Ta5d6ff!isRunningTa5d6ff" Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffprimary-chipTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6ffstartTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffplayTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("ping.start_ping") }}
Tff7b72</button>
Tff7b72<button
v-else
Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chip text-red-600! dark:text-red-300! border-red-200! dark:border-red-500/50!Ta5d6ff"
@Te6edf3click=Ta5d6ff"Ta5d6ffstopTa5d6ff"
Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffpauseTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("ping.stop") }}
Tff7b72</button>
Tff7b72<button Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffsecondary-chipTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6ffclearTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6ffbroomTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("ping.clear_results") }}
Tff7b72</button>
Tff7b72<button Te6edf3type=Ta5d6ff"Ta5d6ffbuttonTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffdanger-chipTa5d6ff" @Te6edf3click=Ta5d6ff"Ta5d6ffdropPathTa5d6ff"Tff7b72>
Tff7b72<MaterialDesignIcon Te6edf3icon-name=Ta5d6ff"Ta5d6fflink-variant-removeTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffw-4 h-4Ta5d6ff" Tff7b72/>
{{ Te6edf3$t("ping.drop_path") }}
Tff7b72</button>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-2 text-xs font-semiboldTa5d6ff"Tff7b72>
Tff7b72<span
Te6edf3:class=Ta5d6ff"Ta5d6ff[
isRunning
? Ta5d6ff'Ta5d6ffbg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-200Ta5d6ff'
Ta5d6ff : Ta5d6ff'Ta5d6ffbg-gray-200 text-gray-700 dark:bg-zinc-800 dark:text-gray-200Ta5d6ff'Ta5d6ff,
Ta5d6ff'Ta5d6ffrounded-full px-3 py-1Ta5d6ff'Ta5d6ff,
]Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("ping.status") }}: {{ isRunning ? Te6edf3$t("ping.running") : Te6edf3$t("ping.idle") }}
Tff7b72</span>
Tff7b72<span
Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary?.durationTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-full px-3 py-1 bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-200Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("ping.last_rtt") }}: {{ lastPingSummary.duration }}
Tff7b72</span>
Tff7b72<span
Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary?.errorTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffrounded-full px-3 py-1 bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-200Ta5d6ff"
Tff7b72>
{{ Te6edf3$t("ping.last_error") }}: {{ lastPingSummary.error }}
Tff7b72</span>
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffglass-card flex flex-col min-h-[320px] space-y-3Ta5d6ff"Tff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6ffflex items-center justify-between gap-4Ta5d6ff"Tff7b72>
Tff7b72<divTff7b72>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-sm font-semibold text-gray-900 dark:text-whiteTa5d6ff"Tff7b72>
{{ Te6edf3$t("ping.console_output") }}
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-xs text-gray-500 dark:text-gray-400Ta5d6ff"Tff7b72>
{{ Te6edf3$t("ping.streaming_responses") }}
Tff7b72</div>
Tff7b72</div>
Tff7b72<div Te6edf3class=Ta5d6ff"Ta5d6fftext-xs text-gray-500 dark:text-gray-400Ta5d6ff"Tff7b72>seq #{{ seq }}Tff7b72</div>
Tff7b72</div>
Tff7b72<div
Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary && !lastPingSummary.errorTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex flex-wrap gap-2 text-xs text-gray-700 dark:text-gray-200Ta5d6ff"
Tff7b72>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary.hopsThere != nullTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffstat-chipTa5d6ff"
Tff7b72>{{ Te6edf3$t("rnprobe.hops") }} there: {{ lastPingSummary.hopsThere }}Tff7b72</span
>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary.hopsBack != nullTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffstat-chipTa5d6ff"
Tff7b72>{{ Te6edf3$t("rnprobe.hops") }} back: {{ lastPingSummary.hopsBack }}Tff7b72</span
>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary.rssi != nullTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffstat-chipTa5d6ff"
Tff7b72>{{ Te6edf3$t("rnprobe.rssi") }} {{ lastPingSummary.rssi }} dBmTff7b72</span
>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary.snr != nullTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffstat-chipTa5d6ff"
Tff7b72>{{ Te6edf3$t("rnprobe.snr") }} {{ lastPingSummary.snr }} dBTff7b72</span
>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary.quality != nullTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffstat-chipTa5d6ff"
Tff7b72>{{ Te6edf3$t("rnprobe.quality") }} {{ lastPingSummary.quality }}%Tff7b72</span
>
Tff7b72<span Te6edf3v-if=Ta5d6ff"Ta5d6fflastPingSummary.viaTa5d6ff" Te6edf3class=Ta5d6ff"Ta5d6ffstat-chipTa5d6ff"
Tff7b72>{{ Te6edf3$t("app.interfaces") }} {{ lastPingSummary.via }}Tff7b72</span
>
Tff7b72</div>
Tff7b72<div
Te6edf3id=Ta5d6ff"Ta5d6ffresultsTa5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffflex-1 overflow-y-auto rounded-2xl bg-black/80 text-emerald-300 font-mono text-xs p-3 space-y-1 shadow-inner border border-zinc-900Ta5d6ff"
Tff7b72>
Tff7b72<div Te6edf3v-if=Ta5d6ff"Ta5d6ffpingResults.length === 0Ta5d6ff" Te6edf3class=Ta5d6ff"Ta5d6fftext-emerald-500/80Ta5d6ff"Tff7b72>
{{ Te6edf3$t("ping.no_pings_yet") }}
Tff7b72</div>
Tff7b72<div
Te6edf3v-for=Ta5d6ff"Ta5d6ff(pingResult, index) in pingResultsTa5d6ff"
Te6edf3:key=Ta5d6ff"Ta5d6ff`Tff7b72${Te6edf3indexTff7b72}Ta5d6ff-Tff7b72${Te6edf3pingResultTff7b72}Ta5d6ff`Ta5d6ff"
Te6edf3class=Ta5d6ff"Ta5d6ffwhitespace-pre-wrapTa5d6ff"
Tff7b72>
{{ pingResult }}
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</div>
Tff7b72</template>
Tff7b72<scriptTff7b72>
import DialogUtils from "../../js/DialogUtils";
import MaterialDesignIcon from "../MaterialDesignIcon.vue";
import ToolsPageHeader from "../tools/ToolsPageHeader.vue";
export default {
name: "PingPage",
components: {
MaterialDesignIcon,
ToolsPageHeader,
},
data() {
return {
isRunning: false,
destinationHash: null,
timeout: 10,
seq: 0,
pingResults: [],
abortController: null,
lastPingSummary: null,
currentSessionId: 0,
};
},
beforeUnmount() {
this.stop();
},
mounted() {
if (this.$route.query.hash) {
this.destinationHash = this.$route.query.hash;
if (this.$route.query.autostart === "1" || this.$route.query.autostart === "true") {
this.start();
}
}
},
methods: {
async start() {
// do nothing if already running
if (this.isRunning) {
return;
}
// simple check to ensure destination hash is valid
if (this.destinationHash == null || this.destinationHash.length !== 32) {
DialogUtils.alert(this.$t("ping.invalid_hash"));
return;
}
// simple check to ensure destination hash is valid
if (this.timeout == null || this.timeout Tff7b72< 1) {
DialogUtils.alert(this.$t("ping.timeout_must_be_number"));
return;
}
// we are now running ping
Te6edf3this.seq = Ta5d6ff0;
Te6edf3this.isRunning = Ta5d6fftrue;
this.currentSessionId++;
const Te6edf3sessionId = Ta5d6ffthis.currentSessionId;
Te6edf3this.abortController = Ta5d6ffnew AbortController();
// run ping until stopped
while (this.isRunning && Te6edf3this.currentSessionId =Ta5d6ff== sessionId) {
// run ping
await this.ping();
// wait a bit before running next ping
if (this.isRunning && Te6edf3this.currentSessionId =Ta5d6ff== sessionId) {
await this.sleep(1000);
}
}
},
async stop() {
Te6edf3this.isRunning = Ta5d6fffalse;
if (this.abortController) {
this.abortController.abort();
}
},
async clear() {
Te6edf3this.pingResults = Ta5d6ff[];
Te6edf3this.lastPingSummary = Ta5d6ffnull;
},
async sleep(millis) {
return new Promise((resolve) =Tff7b72> setTimeout(resolve, millis));
},
async ping() {
try {
this.seq++;
// ping destination
const response = await window.api.get(`/api/v1/ping/${this.destinationHash}/lxmf.delivery`, {
signal: this.abortController.signal,
params: {
timeout: this.timeout,
},
});
const pingResult = response.data.ping_result;
const rttMilliseconds = (pingResult.rtt * 1000).toFixed(3);
const rttDurationString = `${rttMilliseconds}ms`;
const info = [
`seq=${this.seq}`,
`duration=${rttDurationString}`,
`hops_there=${pingResult.hops_there}`,
`hops_back=${pingResult.hops_back}`,
];
// add rssi if available
if (pingResult.rssi != null) {
info.push(`rssi=${pingResult.rssi}dBm`);
}
// add snr if available
if (pingResult.snr != null) {
info.push(`snr=${pingResult.snr}dB`);
}
// add signal quality if available
if (pingResult.quality != null) {
info.push(`quality=${pingResult.quality}%`);
}
// add receiving interface
info.push(`via=${pingResult.receiving_interface}`);
// update ui
this.addPingResult(info.join(" "));
this.lastPingSummary = {
duration: rttDurationString,
hopsThere: pingResult.hops_there,
hopsBack: pingResult.hops_back,
rssi: pingResult.rssi,
snr: pingResult.snr,
quality: pingResult.quality,
via: pingResult.receiving_interface,
};
} catch (e) {
// ignore cancelled error
if (window.api.isCancel?.(e)) {
return;
}
// add ping error to results
const message = e.response?.data?.message ?? e.message ?? String(e);
console.warn("Ping failed:", message);
this.addPingResult(`seq=${this.seq} error=${message}`);
this.lastPingSummary = {
error: typeof message === "string" ? message : JSON.stringify(message),
};
}
},
async dropPath() {
// simple check to ensure destination hash is valid
if (this.destinationHash == null || this.destinationHash.length !== 32) {
DialogUtils.alert(this.$t("ping.invalid_hash"));
return;
}
try {
const response = await window.api.post(`/api/v1/destination/${this.destinationHash}/drop-path`);
DialogUtils.alert(response.data.message);
} catch (e) {
console.log(e);
const message = e.response?.data?.message ?? `Failed to drop path: ${e}`;
DialogUtils.alert(message);
}
},
addPingResult(result) {
this.pingResults.push(result);
this.scrollPingResultsToBottom();
},
scrollPingResultsToBottom: function () {
// next tick waits for the ui to have the new elements added
this.$nextTick(() => {
// set timeout with zero millis seems to fix issue where it doesn't scroll all the way to the bottom...
setTimeout(() => {
const container = document.getElementById("results");
if (container) {
container.scrollTop = container.scrollHeight;
}
}, 0);
});
},
},
};
Tff7b72</script>
Served by rngit 1.5.2 - Generated in 0.03s